#include <nixonbot/core/bncs/protocol/Message.h>

namespace nixonbot {
namespace core {
namespace bncs {
namespace protocol {

QByteArray Message::readStringFrom(QDataStream& ds)
{
	QByteArray ba;
	quint8 c;

	/* Read all the bytes of the string,
	 * stopping after the null terminator. */
	for (ds >> c; c; ds >> c)
	{
		ba.append((char)c);
	}

	return ba;
}

void Message::writeStringTo(QDataStream& ds, QByteArray ba)
{
	int i;
	int size = ba.size();
	
	/* Output the bytes of the string. */
	for (i = 0; i < size; i++)
	{
		ds << (quint8)ba.at(i);
	}
	
	/* Output the null terminator. */
	ds << (quint8)0;
}

QByteArray Message::decodeStatString(QByteArray coded)
{
	/* This algorithm does the reverse of the algorithm described
	 * in encodeStatString. */
	static int blockSize = 8;

	QByteArray plain;

	int blocks = coded.size()/blockSize + 1;
	for (int i = 0; i < blocks; i++)
	{
		int offset = i*8;

		/* The start byte is the byte with the LSBs of the
		 * following 7 bytes in bits 1-7. */
		quint8 start = coded.at(offset);
		if (!(start & 0x00000001))
		{
			/* Invalid encoded statstring! */
			plain.clear();
			return plain;
		}

		/* See how many bytes are in this block.  This should
		 * be at least two! */
		int bytes = coded.size() - offset;
		bytes = (bytes > blockSize) ? blockSize : bytes;
		Q_ASSERT(bytes >= 2 && bytes <= blockSize);

		for (int j = 1; j < bytes; j++)
		{
			quint8 byte = ~0 << 1; //(0xFFFFFFFE)

			/* Get the LSB from the start byte.
			 * Using OR here will set the LSB to whatever
			 * the corresponding bit is in the start byte. */
			byte |= ((start & (1 << j)) >> j);

			/* Get bits 1-7 from the other bytes.
			 * We can just AND here since we know that the
			 * LSB of the other bytes are 1. */
			quint8 other = coded.at(offset + j);
			if (!(other & 0x00000001))
			{
				/* Invalid encoded statstring! */
				plain.clear();
				return plain;
			}
			byte &= other;

			/* Append to plaintext */
			plain.append(byte);
		}
	}

	return plain;
}

QByteArray BncsMessage::encodeStatString(QByteArray& plain)
{
	/* This algorithm takes every block of 7 bytes, sets the LSB of each byte,
	 * takes the original LSBs and puts them in bits 1-7 of an inserted 8th byte,
	 * leaving the LSB of this byte set.
	 *
	 * This algorithm extends any string of n bytes by ceil(n/8) bytes. */
	static int blockSize = 7;

	QByteArray coded;

	int blocks = plain.size()/blockSize + 1;
	for (int i = 0; i < blocks; i++)
	{
		int offset = i*8;
		quint8 start = 0x00000001;

		/* If we have more than blocksize bytes left, then
		 * compute the inserted byte for an entire block,
		 * otherwise just for the remaining bytes. */
		int bytes = plain.size() - offset;
		bytes = (bytes > blockSize) ? blockSize : bytes;

		/* Insert the new byte by inserting the LSB of byte j
		 * of the block (0-6) into bit j + 1 (1-7) of the inserted
		 * byte. */
		for (int j = 0; j < bytes; j++)
		{
			start |= (plain.at(offset + j) & 0x00000001) << (j + 1);
		}
		Q_ASSERT(start & 0x00000001);
		coded.append(start);

		/* Set the LSBs of the remaining bytes and append them to
		 * the coded string. */
		for (int j = 0; j < bytes; j++)
		{
			quint8 byte = plain.at(offset + j);
			byte |= 0x00000001;
			Q_ASSERT(byte & 0x00000001);
			coded.append(byte);
		}
	}

	return coded;
}

QDataStream& operator>> (QDataStream& in, Message& data)
{
	/* Call the virtual deserialization function. */
	data.readFrom(in);
	return in;
}
	
QDataStream& operator<< (QDataStream& out, const Message& data)
{
	/* Call the virtual serialization function. */
	data.writeTo(out);
	return out;
}

}
}
}
}
